#include "shader.h"
#include <fstream>
#include <sstream>

Shader::Shader(const char *vertexPath, const char *fragmentPath)
{
    // 1. retrieve the vertex/fragment source code from filePath
    std::string vertexCode;
    std::string fragmentCode;
    std::ifstream vShaderFile;
    std::ifstream fShaderFile;
    // ensure ifstream objects can throw exceptions:
    vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);
    try
    {
        // open files
        vShaderFile.open(vertexPath);
        fShaderFile.open(fragmentPath);
        std::stringstream vShaderStream, fShaderStream;
        // read file's buffer contents into streams
        vShaderStream << vShaderFile.rdbuf();
        fShaderStream << fShaderFile.rdbuf();
        // close file handlers
        vShaderFile.close();
        fShaderFile.close();
        // convert stream into string
        vertexCode = vShaderStream.str();
        fragmentCode = fShaderStream.str();
    }
    catch (std::ifstream::failure &e)
    {
        LOG_E("ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: {}", e.what());
    }
    const char *vShaderCode = vertexCode.c_str();
    const char *fShaderCode = fragmentCode.c_str();
    buildShader(vShaderCode, fShaderCode);
}
void Shader::buildShader(const char *vShaderCode, const char *fShaderCode)
{ // 2. compile shaders
    unsigned int vertex, fragment;
    // vertex shader
    vertex = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertex, 1, &vShaderCode, NULL);
    glCompileShader(vertex);
    checkCompileErrors(vertex, "VERTEX");
    // fragment Shader
    fragment = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragment, 1, &fShaderCode, NULL);
    glCompileShader(fragment);
    checkCompileErrors(fragment, "FRAGMENT");
    // shader Program
    ID = glCreateProgram();
    glAttachShader(ID, vertex);
    glAttachShader(ID, fragment);
    glLinkProgram(ID);
    checkCompileErrors(ID, "PROGRAM");
    // delete the shaders as they're linked into our program now and no longer necessary
    glDeleteShader(vertex);
    glDeleteShader(fragment);
}
Shader::Shader()
{
    const char *vShaderCode = "#version 330 core\r\n"
                              "layout (location = 0) in vec3 aPos;\r\n"
                              "uniform mat4 camera;\r\n"
                              "void main()\r\n"
                              "{\r\n"
                              "    gl_Position =camera*vec4(aPos, 1.0);\r\n"
                              "}\r\n";

    const char *fShaderCode = "#version 330 core\r\n"
                              "out vec4 FragColor;\r\n"
                              "uniform sampler2D texture_diffuse1;\r\n"
                              "uniform sampler2D texture_diffuse2;\r\n"
                              "uniform sampler2D texture_diffuse3;\r\n"
                              "uniform sampler2D texture_specular1;\r\n"
                              "uniform sampler2D texture_specular2;\r\n"
                              "void main()\r\n"
                              "{\r\n"
                              "    FragColor = vec4(1.0f,0.0f,0.0f,1.0f);\r\n"
                              "}\r\n";
    buildShader(vShaderCode, fShaderCode);
}
Shader::~Shader()
{
    glDeleteProgram(ID);
}

void Shader::setUniformValue(const std::string &name, bool value) const
{
    glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);
}

void Shader::setUniformValue(const std::string &name, int value) const
{
    glUniform1i(glGetUniformLocation(ID, name.c_str()), value);
}

void Shader::setUniformValue(const std::string &name, float value) const
{
    glUniform1f(glGetUniformLocation(ID, name.c_str()), value);
}
void Shader::setUniformValue(const std::string &name, glm::mat4 &value) const
{
    glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, glm::value_ptr(value));
}
void Shader::setUniformValue(const std::string &name, float value[4]) const
{
    glUniform4f(glGetUniformLocation(ID, name.c_str()), value[0], value[1], value[2], value[3]);
}
void Shader::checkCompileErrors(unsigned int shader, std::string type)
{
    int success;
    char infoLog[1024];
    if (type != "PROGRAM")
    {
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (!success)
        {
            glGetShaderInfoLog(shader, 1024, NULL, infoLog);
            LOG_E("ERROR::SHADER_COMPILATION_ERROR of type: {}\n{}\n{}", type, infoLog, " -- --------------------------------------------------- -- ");
        }
    }
    else
    {
        glGetProgramiv(shader, GL_LINK_STATUS, &success);
        if (!success)
        {
            glGetProgramInfoLog(shader, 1024, NULL, infoLog);
            LOG_E("ERROR::PROGRAM_LINKING_ERROR of type: {}\n{}\n{}", type, infoLog, " -- --------------------------------------------------- -- ");
        }
    }
}
